#include <stdio.h>
#include <string.h>

#include "base/base.h"
#include "asm/macroassembler.h"
#include "asm/cpu.h"
#include "asm/dumper.h"
#include "os/os.h"

#include "tests/test-utils.h"

using namespace l8;

static int __cdecl fn0(int a)
{
    return a + 1000;
}

static int __cdecl fn1(int a, int b)
{
    return fn0(a) + b * 2;
}

static void external_call()
{
//    byte * afn = (byte *)&fn1;
//    Dumper::Decode(stdout, afn, afn + 32);
//    return;
    size_t allocated;
    const size_t buf_size = 0x100;
    void * exec_buffer = OS::Allocate(buf_size, &allocated, true);
    ASSERT(allocated >= buf_size);

    Assembler a(exec_buffer, allocated);
    Label for_loop;

#define __ a.

    __ push(ebp);
    __ mov(ebp, esp);

    __ mov(eax, Operand(ebp, 0x8));                         // eax = 1st arguments

    __ push(eax);
    __ push(Immediate(100));
    __ call((Address)&fn1, RelocInfo::EXTERNAL_REFERENCE);
    __ add(Operand(esp), Immediate(0x8));                   // restore stack pointer (sizeof arg1 + sizeof arg2 = 4 + 4 = 8)

    // result in eax, no need to modify
    __ pop(ebp);
    __ ret(0);

#undef __

    CodeDesc desc;
    a.GetCode(&desc);

    Dumper::Decode(stdout, desc.buffer, desc.buffer + desc.instr_size);

    //print_desc(desc, "desc");

    CPU::FlushICache(exec_buffer, allocated);

    typedef int32_t (__cdecl *F0)(int32_t arg);
    F0 func = function_cast<F0>(static_cast<Address>(exec_buffer));

    uint32_t ret = func(100);
    fprintf(stdout, "sum = %d\n\n", ret);

    OS::Free(exec_buffer, allocated);
}

static inline void calculate_sum()
{
    Assembler a(NULL, 0);
    Label for_loop;

#define __ a.

    __ push(ebp);
    __ mov(ebp, esp);
    __ push(ebx);

    __ xor_(eax, Operand(eax));     // eax = 0
    __ mov(ebx, Operand(ebp, 0x8)); // ebx = 1st arguments

    __ bind(&for_loop);             // for:
    __ add(eax, Operand(ebx));      // eax += ebx
    __ dec(ebx);                    // --ebx
    __ j(not_zero, &for_loop);      // if (!ebx) goto for

    __ pop(ebx);
    __ pop(ebp);
    __ ret(0);

#undef __

    CodeDesc desc;
    a.GetCode(&desc);

    //print_desc(desc, "desc");

    Dumper::Decode(stdout, desc.buffer, desc.buffer + desc.instr_size);

    size_t allocated;
    void * exec_buffer = OS::Allocate(desc.instr_size, &allocated, true);

    memcpy(exec_buffer, desc.buffer, desc.instr_size);

    CPU::FlushICache(exec_buffer, allocated);

    typedef int32_t (__cdecl *F0)(int32_t arg);
    F0 func = function_cast<F0>(static_cast<Address>(exec_buffer));

    uint32_t ret = func(100);
    fprintf(stdout, "sum = %d\n\n", ret);

    OS::Free(exec_buffer, allocated);
}

void test()
{
    Assembler a(NULL, 0);

    if (a.pc_offset() != 0x123)
    {
        external_call();
        //calculate_sum();
        return;
    }

    Label nz;

#define __ a.

    __ push(ebp);
    __ mov(ebp, esp);
    __ push(ebx);

    __ mov(ebx, 1000);

    __ mov(eax, Operand(ebp, 8));
    __ dec(eax);
    //__ test(eax, Operand(eax));
    __ j(not_zero, &nz);

    __ mov(ebx, 10);

    __ bind(&nz);

    __ add(eax, Operand(ebx));

    __ pop(ebx);
    __ pop(ebp);
    __ ret(0);

#undef __

    CodeDesc desc;
    a.GetCode(&desc);

    fprintf(stdout, "decoded =\n");
    //Dumper::Decode(stdout, (byte *)&desc1, 10 + (byte *)&desc1);
    Dumper::Decode(stdout, desc.buffer, desc.buffer + desc.instr_size);
    fprintf(stdout, "\n===\n");

    if (desc.instr_size == 20091001)
    {
        return;
    }

    size_t allocated;
    void * exec_buffer = OS::Allocate(desc.instr_size, &allocated, true);

    memcpy(exec_buffer, desc.buffer, desc.instr_size);

    CPU::FlushICache(exec_buffer, allocated);

    typedef int32_t (__cdecl *F0)(int32_t arg);
    F0 func = function_cast<F0>(static_cast<Address>(exec_buffer));

    uint32_t ret = func(1);
    fprintf(stdout, "r = %d\n\n", ret);

    OS::Free(exec_buffer, allocated);
}

